package com.oneinlet

import java.io.*
import java.net.HttpURLConnection
import java.net.URL
import java.net.URLEncoder
import java.nio.charset.Charset


/**
 * Created by WangZiHe on 2018/5/13
 * QQ/WeChat:648830605
 * QQ-Group:368512253
 * Blog:www.520code.net
 * Github:https://github.com/yancheng199287
 */

/**
 * http请求帮手，帮助你人性化简单处理各种请求，json，xml，form，form-data
 * @author  WangZihe
 */
object YCHttp {

    private val DEFAULT_CHARSET: Charset = Charset.forName("UTF-8")

    enum class ContentType(val contentType: String) {
        /*** 适合提交JSON数据  ***/
        JSON("application/json"),

        /*** 适合提交XML数据  ***/
        XML("application/xml"),

        /*** 普通表单提交  ***/
        FORM_URL("application/x-www-form-urlencoded"),

        /*** 含有文件内容的表单提交  ***/
        FORM_DATA("multipart/form-data"),
    }

    enum class Method {
        GET,
        POST,
        PUT,
        DELETE,
        OPTIONS,
        HEAD,
        TRACE, ;
    }

    private fun getHttpURLConnection(postUrl: String, contentType: ContentType, method: Method = Method.POST, header: Map<String, String>? = null, boundary: String? = null): HttpURLConnection {
        //  把proxy对象传入 url.openConnection(proxy) 可以配合fiddle监控该请求
        // val sa = InetSocketAddress("192.168.31.92", 1234)
        // val proxy = Proxy(Proxy.Type.HTTP, sa)

        // 发送POST请求  建立连接
        val url = URL(postUrl)
        // val conn = url.openConnection(proxy) as HttpURLConnection
        val conn = url.openConnection() as HttpURLConnection
        // 设置URL请求方法
        conn.requestMethod = method.name

        if (ContentType.FORM_DATA == contentType) {
            // 边界标识 随机生成
            // val boundary = UUID.randomUUID().toString()
            conn.setRequestProperty("Content-Type", "${contentType.contentType};boundary=$boundary")
        } else {
            //发送请求参数类型
            conn.setRequestProperty("Content-Type", contentType.contentType)
        }

        // 自定义头部属性
        if (YCollections.isNotEmpty(header)) {
            for (k in header!!.keys) {
                conn.setRequestProperty(k, header[k])
            }
        }
        // 维持长连接
        conn.setRequestProperty("Connection", "Keep-Alive")
        // 设置代理标识
        conn.setRequestProperty("User-Agent", "YC-HTTP-CLIENT(1.0.0)")
        // 设置编码
        conn.setRequestProperty("Charset", "UTF-8")
        // 设置接收数据类型
        conn.setRequestProperty("Accept", "*/*")
        //是否使用缓存
        conn.useCaches = false
        //是否需要输出,写入body数据，GET不需要
        if (Method.GET != method) {
            conn.doOutput = true
        }
        //连接超时
        conn.connectTimeout = 5000
        //读取超时
        conn.readTimeout = 10000
        //允许自动重定向
        conn.instanceFollowRedirects = true

        conn.connect()

        return conn
    }

    private fun getPostResponseResult(requestData: String, conn: HttpURLConnection): String {
        OutputStreamWriter(conn.outputStream, DEFAULT_CHARSET).use {
            //将请求的内容写入到输出流
            it.write(requestData)
            //自动将缓冲区数据提交关闭输出流，
            it.close()
        }

        checkException(conn)
        //如果响应状态码200，读取返回的响应数据
        return YCStream.toString(conn.inputStream)
    }

    private fun getResponseResult(conn: HttpURLConnection): String {

        checkException(conn)
        //如果响应状态码200，读取返回的响应数据
        return YCStream.toString(conn.inputStream)
    }


    private fun getFormDataResponseResult(conn: HttpURLConnection, boundary: String, map: Map<String, String>, mapFile: Map<String, File>?): String {
        //boundary就是request头和上传文件内容的分隔符
        val out = DataOutputStream(conn.outputStream)

        if (YCollections.isNotEmpty(map)) {
            val sb = StringBuilder()
            for ((key, value) in map) {
                if (!YCString.hasText(key)) {
                    continue
                }
                sb.append("\r\n").append("--").append(boundary).append("\r\n")
                sb.append("Content-Disposition: form-data; name=\"$key\"\r\n\r\n")
                sb.append(value)
            }
            out.write(sb.toString().toByteArray())
        }

        if (mapFile != null) {
            for ((key, value) in mapFile) {
                if (!YCString.hasText(key)) {
                    continue
                }
                val sb = StringBuilder()
                sb.append("\r\n").append("--").append(boundary).append("\r\n")
                sb.append("Content-Disposition: form-data; name=\"$key\"; filename=\"${value.name}\"\r\n\r\n")
                // 内容类型，不确定就不需要指定
                // sb.append("Content-Type:application/octet-stream\r\n\r\n")
                //sb.append("Content-Type:text/plain\r\n\r\n")
                out.write(sb.toString().toByteArray())
                YCStream.write(FileInputStream(value), out)
            }
        }

        val b = ("\r\n\r\n").plus("--").plus(boundary)
        out.write(b.toByteArray())

        checkException(conn)

        return YCStream.toString(conn.inputStream)
    }


    /**
     * 发送GET请求 获取返回的流，一般适合用于获取文件
     * @param requestUrl 请求链接地址，参数请跟随在url上 如 http://520code.net?name=linux
     * */
    fun doGetInputStream(requestUrl: String): InputStream {
        return getHttpURLConnection(requestUrl, ContentType.FORM_URL, Method.GET, null).inputStream
    }


    /**
     * 发送GET请求 获取返回的流，一般适合用于获取文件
     * @param requestUrl 请求链接地址，参数请跟随在url上 如 http://520code.net?name=linux
     * */
    fun doGetInputStream(requestUrl: String, header: Map<String, String>? = null): InputStream {
        return getHttpURLConnection(requestUrl, ContentType.FORM_URL, Method.GET, header).inputStream
    }

    /**
     * 发送GET请求
     * @param requestUrl 请求链接地址，参数请跟随在url上 如 http://520code.net?name=linux
     * */
    fun doGet(requestUrl: String): String {
        return doGet(requestUrl, null, null)
    }


    /**
     * 发送GET请求
     * @param requestUrl 请求链接地址，最后一个字符不要带?
     * @param map 请求参数，会自动封装成 key=value&key=value 形式加在url后面
     * */
    // 兼容java 不用传递header参数
    fun doGet(requestUrl: String, map: Map<String, String>?): String {
        return doGet(requestUrl, map, null)
    }

    fun doGet(requestUrl: String, map: Map<String, String>?, header: Map<String, String>? = null): String {
        var url = requestUrl
        if (map != null) {
            url = requestUrl.plus("?").plus(getParameter(map))
        }
        return getResponseResult(getHttpURLConnection(url, ContentType.FORM_URL, Method.GET, header))
    }


    /**
     * 发送GET请求
     * @param requestUrl 请求链接地址
     * @param postData json数据
     * */
    // 兼容java 不用传递header参数
    fun doPostJSON(requestUrl: String, postData: String): String {
        return doPostJSON(requestUrl, postData, null)
    }

    fun doPostJSON(requestUrl: String, postData: String, header: Map<String, String>? = null): String {
        return getPostResponseResult(postData, getHttpURLConnection(requestUrl, ContentType.JSON, Method.POST, header))
    }

    /**
     * 发送GET请求
     * @param requestUrl 请求链接地址
     * @param postData xml数据
     * */
    // 兼容java 不用传递header参数
    fun doPostXML(requestUrl: String, postData: String): String {
        return doPostXML(requestUrl, postData, null)
    }

    fun doPostXML(requestUrl: String, postData: String, header: Map<String, String>? = null): String {
        return getPostResponseResult(postData, getHttpURLConnection(requestUrl, ContentType.XML, Method.POST, header))
    }


    fun doPost(requestUrl: String, postData: String, header: Map<String, String>? = null, contentType: ContentType, method: Method): String {
        return getPostResponseResult(postData, getHttpURLConnection(requestUrl, contentType, method, header))
    }


    /**
     * 发送POST请求，表单提交，url编码
     * @param requestUrl 请求链接地址
     * @param formUrlData 表单数据 url编码 键值对，调用此方便前，请务必进行URL参数编码
     * */
    // 兼容java 不用传递header参数
    fun doPostFormUrl(requestUrl: String, formUrlData: String): String {
        return doPostFormUrl(requestUrl, formUrlData, null)
    }

    fun doPostFormUrl(requestUrl: String, formUrlData: String, header: Map<String, String>? = null): String {
        return getPostResponseResult(formUrlData, getHttpURLConnection(requestUrl, ContentType.FORM_URL, Method.POST, header))
    }

    /**
     * 发送POST请求，表单提交，url编码
     * @param requestUrl 请求链接地址
     * @param map 表单数据请求参数，会自动封装成 key=value&key=value，无需URL参数编码，该方法会自动编码
     * */

    // 兼容java 不用传递header参数
    fun doPostFormUrl(requestUrl: String, map: Map<String, String>): String {
        return doPostFormUrl(requestUrl, map, null)
    }

    fun doPostFormUrl(requestUrl: String, map: Map<String, String>, header: Map<String, String>? = null): String {
        return getPostResponseResult(getParameter(map), getHttpURLConnection(requestUrl, ContentType.FORM_URL, Method.POST, header))
    }

    /**
     * 发送POST请求，表单提交，form data编码 适合有文件的表单提交，可以同时支持多文件，多参数
     * @param requestUrl 请求链接地址
     * @param map 表单数据请求参数
     * @param mapFile 多个文件，支持多文件上传
     * @param header http协议头额外自定义
     * */
    // 兼容java 不用传递header参数
    fun doPostFormData(requestUrl: String, map: Map<String, String>, mapFile: Map<String, File>?): String {
        return doPostFormData(requestUrl, map, mapFile, null)
    }

    fun doPostFormData(requestUrl: String, map: Map<String, String>, mapFile: Map<String, File>?, header: Map<String, String>? = null): String {
        val boundary = "------YCHTTPFormBoundary" + YCString.generateRandomLetter(5)
        return getFormDataResponseResult(getHttpURLConnection(requestUrl, ContentType.FORM_DATA, Method.POST, header, boundary), boundary, map, mapFile)
    }


    fun getParameter(map: Map<String, String>): String {
        val sb = StringBuilder()
        val size = map.size
        var i = 0
        for ((k, v) in map) {
            sb.append(URLEncoder.encode(k, DEFAULT_CHARSET.displayName()))
            sb.append("=")
            sb.append(URLEncoder.encode(v, DEFAULT_CHARSET.displayName()))
            i++
            if (i != size) {
                sb.append("&")
            }
        }
        return sb.toString()
    }

    /**
     *   对于非200的请求，抛出异常信息，信息内容由 请求地址 url  相应代码 code  错误信息 error
     *   三部分组成，方便开发者调试信息或者做异常判断处理，如需额外的数据，开发者在请求的时候可以自行记录
     *
     *   提示： 三部分信息是按照 key=value,key=value 格式组成，特别适合分割，获取指定有用信息
     * **/
    private fun checkException(conn: HttpURLConnection) {
        //如果响应状态码非200，抛出异常信息
        if (conn.responseCode != HttpURLConnection.HTTP_OK) {
            // val result = YCStream.toString(conn.inputStream)
            var error: String? = null
            val input = conn.errorStream
            if (input != null) {
                error = YCStream.toString(input)
            }
            throw RuntimeException("请求发生异常：请求地址：${conn.url}，返回响应码：${conn.responseCode},返回响应消息：${error}, ")
        }
    }

}
